﻿#region Header

/*
Behavioral Rating of Dancing Human Crowds based on Motion Patterns
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using Emgu.CV;
using Emgu.CV.Structure;
using paravili.Services;
using paravili.Steps;

#endregion

namespace paravili.CaptureSources {
	/// <summary>
	/// offers the functionallty to generate images sequences on the fly based on 
	/// - manual entered <see cref="IDrawableElement"/>s 
	/// - or a given generator
	/// </summary>
	public class OnTheFlyGeneratedImagesAsCaptureSource : IDisposableCaptureSource {
		#region Public Properties

		/// <summary>
		/// defines the height of the image to generate
		/// </summary>
		[ConfigurableValue]
		public int ImageHeight { get; set; }

		/// <summary>
		/// defines the width of the images to generate
		/// </summary>
		[ConfigurableValue]
		public int ImageWidth { get; set; }

		/// <summary>
		/// generater used to generate moving elements
		/// </summary>
		public IMovingElementGenerator Generator { get; set; }

		[ConfigurableNumericValue(Name = "min velocity", RangeFrom = 0, RangeTo = 10)]
		public int MinVelocity { get; set; }

		[ConfigurableNumericValue(Name = "max velocity", RangeFrom = 1, RangeTo = 50)]
		public int MaxVelocity { get; set; }

		/// <summary>
		/// available elements
		/// </summary>
		[ConfigurableValue]
		public ObservableCollection<IDrawableElement> MovingElements { get; set; }

		/// <summary>
		/// use generator if true, otherwise, set <see cref="MovingElements"/> manually
		/// </summary>
		[ConfigurableValue]
		public bool UseGenerator { get; set; }

		#endregion

		#region Public Methods

		public OnTheFlyGeneratedImagesAsCaptureSource() {
			MinVelocity = 0;
			MaxVelocity = 10;
			ImageWidth = 320;
			ImageHeight = 240;
			MovingElements = new ObservableCollection<IDrawableElement>();
			Generator = new CircleGenerator();
			UseGenerator = true;
		}

		#endregion

		#region Private Methods

		private Image<Bgr, byte> CreatePicture() {
			var img = new Image<Bgr, byte>(ImageWidth, ImageHeight);

			foreach (IDrawableElement c in MovingElements) {
				c.DrawImageForCurrentFrame(img);
			}
			return img;
		}

		private void MutateCircles() {
			foreach (IDrawableElement c in MovingElements) {
				c.CalculateForNextFrame(ImageWidth, ImageHeight, MinVelocity, MaxVelocity);
			}
		}

		#endregion

		#region ICaptureSource

		[ConfigurableValue]
		public string Name { get; set; }

		public bool IsAvailable {
			get { return true; }
		}

		public Image<Bgr, byte> QueryImage() {
			// mutate circles
			MutateCircles();
			// CreatePicture
			return CreatePicture();
		}

		public void DoInit() {
			if (UseGenerator) {
				IList<IDrawableElement> items = Generator.Generate(ImageHeight, ImageWidth);
				foreach (IDrawableElement i in items) {
					MovingElements.Add(i);
				}
			}
		}

		public void DoCleanUp() {
			if (UseGenerator) {
				MovingElements.Clear();
			}
		}

		#endregion
	}

	/// <summary>
	/// used to generate <see cref="IDrawableElement"/>
	/// </summary>
	public interface IMovingElementGenerator {
		IList<IDrawableElement> Generate(int imageWidth, int imageHeight);
	}

	/// <summary>
	/// generates circles
	/// </summary>
	public class CircleGenerator : IMovingElementGenerator {
		#region Public Properties

		[ConfigurableValue]
		public int MaxCircles { get; set; }
		[ConfigurableValue]
		public int MinCircles { get; set; }
		[ConfigurableValue]
		public int MaxCirclesSize { get; set; }
		[ConfigurableValue]
		public int MinCirclesSize { get; set; }
		[ConfigurableValue]
		public bool RandomizeColour { get; set; }

		#endregion

		#region Public Methods

		public CircleGenerator() {
			MaxCircles = 50;
			MinCircles = 3;
			RandomizeColour = true;
			MinCirclesSize = 3;
			MaxCirclesSize = 20;
		}

		#endregion

		#region IMovingElementGenerator Members

		public IList<IDrawableElement> Generate(int imageWidth, int imageHeight) {
			IList<IDrawableElement> toReturn = new List<IDrawableElement>();
			// generate circle
			int circles = new Random().Next(MinCircles, MaxCircles);
			var r = new Random();
			for (int i = 0; i < circles; i++) {
				var colour = new Bgr(255, 255, 255);
				if (RandomizeColour) {
					colour = new Bgr(r.Next(0, 255), r.Next(0, 255), r.Next(0, 255));
				}
				toReturn.Add(new RandomizedCircle {
					Point = new Point(r.Next(0, imageWidth), r.Next(0, imageHeight)),
					Radius = r.Next(MinCirclesSize, MaxCirclesSize),
					Colour = colour
				});
			}
			return toReturn;
		}

		#endregion
	}

	/// <summary>
	/// represents a elment that is drawable
	/// </summary>
	public interface IDrawableElement {
		void DrawImageForCurrentFrame(Image<Bgr, Byte> image);
		void CalculateForNextFrame(int imageWidth, int imageHeight, int minVelocity, int maxVelocity);
	}

	/// <summary>
	/// represents a abstract circle that contains a middle point, radius and a colour
	/// </summary>
	public abstract class Circle : IDrawableElement {
		#region Public Properties

		[ConfigurableValue]
		public Point Point { get; set; }
		[ConfigurableValue]
		public int Radius { get; set; }
		[ConfigurableValue]
		public Bgr Colour { get; set; }

		#endregion

		#region IDrawableElement Members

		public void DrawImageForCurrentFrame(Image<Bgr, byte> image) {
			image.Draw(new Ellipse(new PointF(Point.X, Point.Y), new SizeF(Radius, Radius), 0f), Colour, 5);
		}

		public abstract void CalculateForNextFrame(int imageWidth, int imageHeight, int minVelocity, int maxVelocity);

		#endregion
	}

	/// <summary>
	/// represents a circle that is moving in a deterministic way
	/// </summary>
	public class DeterministicCircle : Circle {
		#region Public Properties
		[ConfigurableValue]
		public int XOffset { get; set; }
		[ConfigurableValue]
		public int YOffset { get; set; }
		[ConfigurableValue]
		public BounderyBehavior Behavior { get; set; }
		[ConfigurableValue]
		public int BlueColor { get; set; }
		[ConfigurableValue]
		public int RedColor { get; set; }
		[ConfigurableValue]
		public int GreenColor { get; set; }

		#endregion

		#region Public Methods

		public DeterministicCircle() {
			BlueColor = 255;
			RedColor = 255;
			GreenColor = 255;
		}

		public override void CalculateForNextFrame(int imageWidth, int imageHeight, int minVelocity, int maxVelocity) {
			Colour = new Bgr(BlueColor, GreenColor, RedColor);
			switch (Behavior) {
				case BounderyBehavior.InverseOffset:
					Point = Point.Add(Point, new Size(XOffset, YOffset));
					if (Point.X + Radius < 0 || Point.X - Radius > imageWidth) {
						XOffset *= -1;
					}
					if (Point.Y + Radius < 0 || Point.Y - Radius > imageHeight) {
						YOffset *= -1;
					}
					break;
				case BounderyBehavior.ModuloValues:
					Point = new Point((Point.X + XOffset) % imageWidth, (Point.Y + YOffset) % imageHeight);
					break;
			}
		}

		#endregion

		#region Public Enums

		public enum BounderyBehavior {
			ModuloValues,
			InverseOffset,
		}

		#endregion
	}

	/// <summary>
	/// represents a circle that is moving in a deterministic way
	/// </summary>
	public class NervousToLinearCircle : Circle {
		#region Public Properties

		public Point OriginalPoint;
		[ConfigurableValue]
		public int XOffset { get; set; }
		[ConfigurableValue]
		public int YOffset { get; set; }
		[ConfigurableValue]
		public int BlueColor { get; set; }
		[ConfigurableValue]
		public int RedColor { get; set; }
		[ConfigurableValue]
		public int GreenColor { get; set; }
		[ConfigurableValue]
		public int Speed { get; set; }
		[ConfigurableValue]
		public int Distance { get; set; }
		[ConfigurableValue]
		public int NumberOfFramesAfterBehaviourChanging { get; set; }



		#endregion

		private bool m_isMovingLinear;
		private int XDirection { get; set; }
		private int YDirection { get; set; }
		private int m_frameNr;
		#region Public Methods

		public NervousToLinearCircle() {
			BlueColor = 255;
			RedColor = 255;
			GreenColor = 255;
			Distance = 10;
			Speed = 1;
			NumberOfFramesAfterBehaviourChanging = 25;
			m_isMovingLinear = true;
		}

		public override void CalculateForNextFrame(int imageWidth, int imageHeight, int minVelocity, int maxVelocity) {
			if (OriginalPoint == default(Point)) {
				OriginalPoint = Point;
			}
			m_frameNr++;
			if (m_frameNr % NumberOfFramesAfterBehaviourChanging == 0) {
				m_isMovingLinear = !m_isMovingLinear;
			}

			Colour = new Bgr(BlueColor, GreenColor, RedColor);
			if (m_isMovingLinear) {
				XDirection = 1;
				YDirection = 0;
			}
			else {
				Random r = new Random();
				XDirection = r.Next(-Distance, Distance);
				YDirection = r.Next(-Distance, Distance);
			}
			Point = Point.Add(Point, new Size(Speed * XDirection, Speed * YDirection));

		}

		#endregion

		#region Public Enums

		public enum BounderyBehavior {
			ModuloValues,
			InverseOffset,
		}

		#endregion
	}

	/// <summary>
	/// represents a circle that is moving in a deterministic way
	/// </summary>
	public class DeterministicNervousCircleHorizontal : Circle {
		#region Public Properties

		public Point OriginalPoint;
		[ConfigurableValue]
		public int XOffset { get; set; }
		[ConfigurableValue]
		public int YOffset { get; set; }
		[ConfigurableValue]
		public int BlueColor { get; set; }
		[ConfigurableValue]
		public int RedColor { get; set; }
		[ConfigurableValue]
		public int GreenColor { get; set; }
		[ConfigurableValue]
		public int Speed { get; set; }
		[ConfigurableValue]
		public int Distance { get; set; }

		#endregion

		private int Direction { get; set; }

		#region Public Methods

		public DeterministicNervousCircleHorizontal() {
			BlueColor = 255;
			RedColor = 255;
			GreenColor = 255;
		}

		public override void CalculateForNextFrame(int imageWidth, int imageHeight, int minVelocity, int maxVelocity) {
			if (OriginalPoint == default(Point)) {
				OriginalPoint = Point;
			}

			Colour = new Bgr(BlueColor, GreenColor, RedColor);

			Point = Point.Add(Point, new Size(Speed * Direction, 0));
			if ((Point.X - OriginalPoint.X) > Distance) {
				Direction = -1;
			}
			else {
				Direction = 1;
			}
		}

		#endregion

		#region Public Enums

		public enum BounderyBehavior {
			ModuloValues,
			InverseOffset,
		}

		#endregion
	}

	/// <summary>
	/// represents a circle that moves in a randomized way
	/// </summary>
	internal class RandomizedCircle : Circle {
		#region Public Methods

		public override void CalculateForNextFrame(int imageWidth, int imageHeight, int minVelocity, int maxVelocity) {
			var r = new Random();
			bool xbOffset = r.Next(0, 11) > 5;
			bool ybOffset = r.Next(0, 11) > 5;
			int xOffset = r.Next(minVelocity, maxVelocity) * (xbOffset ? -1 : 1);
			int yOffset = r.Next(minVelocity, maxVelocity) * (ybOffset ? -1 : 1);
			Point = new Point(Point.X + xOffset, Point.Y + yOffset);
			if (Point.X + Radius < 0 || Point.X - Radius > imageWidth || Point.Y + Radius < 0 || Point.Y - Radius > imageHeight) {
				Point = new Point(r.Next(0, imageWidth), r.Next(0, imageHeight));
			}
		}

		#endregion
	}
}